package uk.ac.ebi.microarray.expressionprofiler.ra.fs.impl;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.VFS;
import org.apache.commons.vfs.impl.DefaultFileReplicator;
import org.apache.commons.vfs.impl.StandardFileSystemManager;

import uk.ac.ebi.microarray.expressionprofiler.ra.fs.FileSystemConnectionInfo;

public class FileSystemManagedConnection implements ManagedConnection {

    private static final String VFS_TEMP = "vfs_temp";

    private PrintWriter writer;
    private List<ConnectionEventListener> listeners;
    private Set<FileSystemConnectionImpl> connectionSet;
    FileSystemManager fsManager;

    private final FileSystemConnectionInfo info;
    private final String rootpath;
    private final boolean isUserFolder;

    public FileSystemManagedConnection(ConnectionRequestInfo conReqInfo, PrintWriter writer,
            FileSystemManagedConnectionFactory mcFactory) throws ResourceException {
        this.writer = writer;
        this.listeners = new ArrayList<ConnectionEventListener>();
        this.connectionSet = new HashSet<FileSystemConnectionImpl>();
        this.info = (FileSystemConnectionInfo) conReqInfo;
        if (info.isDirAbsolute()) {
            rootpath = info.getDir();
            isUserFolder = true;
        } else {
            rootpath = mcFactory.getRepositoryPath() + File.separator + info.getDir()
                    + (info.isUniqueIdentifierNecessary() ? ("." + UUID.randomUUID().toString() + File.separator) : "");
            isUserFolder = !info.isUniqueIdentifierNecessary();
        }
        try {
            StandardFileSystemManager fsm = (StandardFileSystemManager) VFS.getManager();
            File tempDir = new File(mcFactory.getRepositoryPath() + File.separator + VFS_TEMP);
            tempDir.mkdir();
            fsm.setTemporaryFileStore(new DefaultFileReplicator(tempDir));
            fsManager = fsm;
        } catch (FileSystemException e) {
            throw new ResourceException(e);
        }

    }

    public Object getConnection(Subject subj, ConnectionRequestInfo conReqInfo) throws ResourceException {
        FileSystemConnectionImpl conn = new FileSystemConnectionImpl(this);
        addConnection(conn);
        return conn;
    }

    public void destroy() throws ResourceException {
        invalidateAllConnections();
        synchronized (this.listeners) {
            listeners = null;
        }
    }

    public void cleanup() throws ResourceException {
        invalidateAllConnections();
    }

    public void associateConnection(Object conn) throws ResourceException {
        if (!(conn instanceof FileSystemConnectionImpl)) {
            throw new ResourceException("Connection has an incorrect type");
        }
        ((FileSystemConnectionImpl) conn).associateConnection(this);
    }

    public void addConnectionEventListener(ConnectionEventListener listener) {
        synchronized (this.listeners) {
            listeners.add(listener);
        }
    }

    public void removeConnectionEventListener(ConnectionEventListener listener) {
        synchronized (this.listeners) {
            listeners.remove(listener);
        }
    }

    public XAResource getXAResource() throws ResourceException {
        throw new NotSupportedException("XA transactions are not supported");
    }

    public LocalTransaction getLocalTransaction() throws ResourceException {
        throw new NotSupportedException("Transactions are not supported");
    }

    public ManagedConnectionMetaData getMetaData() throws ResourceException {
        return new FileSystemManagedConnectionMetaData();
    }

    public void setLogWriter(PrintWriter out) throws ResourceException {
        this.writer = out;
    }

    public PrintWriter getLogWriter() throws ResourceException {
        return writer;
    }

    void removeConnection(FileSystemConnectionImpl con) {
        synchronized (this.connectionSet) {
            connectionSet.remove(con);
        }
    }

    void addConnection(FileSystemConnectionImpl con) {
        synchronized (this.connectionSet) {
            connectionSet.add(con);
        }
    }

    void invalidateAllConnections() {
        synchronized (this.connectionSet) {
            for (FileSystemConnectionImpl con : connectionSet) {
                con.invalidate();
            }
            connectionSet.clear();
        }
    }

    public void close(FileSystemConnectionImpl con) {
        if (!isUserFolder) {
            try {
                FileUtils.deleteDirectory(new File(rootpath));

            } catch (IOException e) {
                e.printStackTrace(writer);
            }
        }
        ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
        event.setConnectionHandle(con);
        synchronized (this.listeners) {
            Iterator<? extends Object> itr = listeners.iterator();
            while (itr.hasNext()) {
                try {
                    ((ConnectionEventListener) itr.next()).connectionClosed(event);
                } catch (Throwable e) {
                    e.printStackTrace(writer);
                }
            }
        }
        con.invalidate();
        removeConnection(con);
    }

    public List<String> listDirs(FileObject f) throws FileSystemException {
        List<String> files = new ArrayList<String>();
        for (FileObject g : f.getChildren()) {
            if (g.getType().equals(FileType.FOLDER)) {
                files.add(g.getName().getBaseName());
            } else {
                String ext = g.getName().getExtension();
                if (ext.equalsIgnoreCase("tar")) {
                    files.addAll(listDirs(fsManager.createFileSystem(g)));
                }
            }
        }
        return files;
    }

    public List<String> listDirs(FileObject f, String regexp) throws FileSystemException {
        List<String> dirs = new ArrayList<String>();
        for (FileObject child : f.getChildren()) {
            String name = child.getName().getBaseName();
            if (child.getType().equals(FileType.FOLDER) && name.matches(regexp)) {
                dirs.add(name);
            }
        }
        return dirs;
    }

    public List<String> listFiles(FileObject f, String regexp) throws FileSystemException {
        List<String> files = new ArrayList<String>();
        for (FileObject child : f.getChildren()) {
            String name = child.getName().getBaseName();
            String ext = child.getName().getExtension();
            if (child.getType().equals(FileType.FILE) && name.matches(regexp)) {
                if (ext.equalsIgnoreCase("gz")) {
                    files.addAll(listFiles(fsManager.createFileSystem("gz", child), ".*"));
                } else {
                    files.add(name);
                }
            }
        }
        return files;
    }

    public FileObject getInitialDir() throws FileSystemException {
        return fsManager.resolveFile(rootpath);
    }

    public FileObject getDir(FileObject parent, String dir) throws FileSystemException {
        FileObject nd;
        if (dir.equals("..")) {
            nd = parent.getParent();

        } else {
            nd = parent.getChild(dir);
        }
        return nd;
    }

    public FileObject getDirOrTar(FileObject parent, String dir) throws FileSystemException {
        FileObject nd = getDir(parent, dir);
        if (nd != null) {
            return nd;
        }
        //checking for tar files
        FileObject compressed = parent.getChild("compressed");
        if (compressed == null) {
            return null;
        }
        for (FileObject child : compressed.getChildren()) {
            String name = child.getName().getBaseName();
            String ext = child.getName().getExtension();
            if (ext.equals("tar") && name.contains(dir)) {
                FileObject tarFile = fsManager.createFileSystem(child);
                return tarFile.getChild(dir);
            }
        }
        return null;
    }

    public FileObject getFile(FileObject dir, String file) throws FileSystemException {
        FileObject child = dir.getChild(file);
        if (child != null) {
            return child;
        }
        child = dir.getChild(file + ".gz");
        if (child != null) {
            child = fsManager.createFileSystem("gz", child);
            return child.getChild(file);
        }
        return null;
    }
}
